﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Mail;
using DotNetNuke.Common.Utilities;
using DotNetNuke.Entities.Portals;
using DotNetNuke.Entities.Users;
using DotNetNuke.Entities.Profile;
using DotNetNuke.Security.Roles;
using DotNetNuke.Services.Mail;
using DnnMail = DotNetNuke.Services.Mail;
using DotNetNuke.Services.Messaging;
using DotNetNuke.Services.Tokens;

namespace WESNet.DNN.Modules.UserExport
{
    public class Mailer : IDisposable
    {
        public enum SendMethods
        {
            Send_Default = 0,
            Send_TO,
            Send_CC,
            Send_BCC
        }

        public enum AddressFields
        {
            TO = 0,
            CC,
            BCC
        }

        #region Private Members

        private bool debugWrite = false;

        private readonly List<UserInfo>[]  _recipientLists = {new List<UserInfo>(), new List<UserInfo>(), new List<UserInfo>()};

        private readonly List<string> _recipientKeys = new List<string>();

        private readonly List<Attachment> _attachments = new List<Attachment>();

        private readonly Dictionary<string, Object> _tokenizationDataSources = new Dictionary<string, Object>();

        private bool _isDisposed;
        private bool _recipientListsBuilt;

        private PortalSettings _portalSettings;
        private int _portalId;
        private int _moduleId;

        private UserInfo _sendingUser;
        private string _sendingUserLanguage;
        private string _body;
        #endregion

        #region Public Properties

        public int PortalId
        {
            get
            {
                return _portalId;
            }
            set
            {
                _portalId = value;
                _portalSettings = new PortalSettings(_portalId);
            }
        }

        public string EmailTo { get; set; }
        public string EmailCC { get; set; }
        public string EmailBCC { get; set; }
        public List<string> EmailToRoles { get; set; }
        public AddressFields EmailToRolesAddressField { get; set; }
        public SendMethods SendMethod { get; set; }
        
        public string EmailFrom { get; set; }
        public string EmailReplyTo { get; set; }
        public char EmailSeparater { get; set; }
        public bool RemoveDuplicates { get; set; }
        public bool ReplaceTokens { get; set; }
        public DnnMail.MailPriority MailPriority { get; set; }
        public bool SendAsynch { get; set; }
        
        public UserInfo SendingUser
        {
            get
            {
                return _sendingUser;
            }
            set
            {
                _sendingUser = value;
                if (_sendingUser.Profile.PreferredLocale != null)
                {
                    _sendingUserLanguage = _sendingUser.Profile.PreferredLocale;
                }
                else
                {
                    PortalSettings portalSettings = PortalController.Instance.GetCurrentPortalSettings();
                    _sendingUserLanguage = portalSettings.DefaultLanguage;
                }
            }
        }

        public MailFormat BodyFormat { get; set; }
        public string Subject { get; set; }
        public string Body
        {
            get
            {
                return _body;
            }
            set
            {
                _body = value;
                BodyFormat = HtmlUtils.IsHtml(_body) ? MailFormat.Html : MailFormat.Text;
            }
        }

        public List<Attachment> Attachments
        {
            get
            {
                return _attachments;
            }
        }

        public Dictionary<string, Object> TokenizationDataSources
        {
            get
            {
                return _tokenizationDataSources;
            }
        }

        #endregion

        #region Constructors

        public Mailer(int moduleId)
        {
            _portalSettings = PortalController.Instance.GetCurrentPortalSettings();
            _portalId = _portalSettings.PortalId;
            _moduleId = moduleId;
            SendingUser = UserController.Instance.GetCurrentUserInfo();
            RemoveDuplicates = true;
            EmailSeparater = ';';
            BodyFormat = MailFormat.Html;
            EmailToRolesAddressField = AddressFields.TO;
            SendMethod = SendMethods.Send_Default;
            MailPriority = DnnMail.MailPriority.Normal;
            ReplaceTokens = true;
        }

        public Mailer(int moduleId, int portalId)
            : this(moduleId)
        {
            PortalId = portalId;
        }

        #endregion

        #region Private Methods

        private void ClearRecipients()
        {
            foreach (int fld in Enum.GetValues(typeof (AddressFields)))
            {
                _recipientLists[fld].Clear();
            }
            _recipientKeys.Clear();
        }

        private void AddRecipients(AddressFields fld)
        {
            string emailField;
            List<UserInfo> recipientList;

            switch (fld)
            {
                case AddressFields.TO:
                    emailField = EmailTo;
                    recipientList = _recipientLists[(int)AddressFields.TO];
                    break;
                case AddressFields.CC:
                    emailField = EmailCC;
                    recipientList = _recipientLists[(int)AddressFields.CC];
                    break;
                case AddressFields.BCC:
                    emailField = EmailBCC;
                    recipientList = _recipientLists[(int)AddressFields.BCC];
                    break;
                default:
                    emailField = EmailTo;
                    recipientList = _recipientLists[(int)AddressFields.TO];
                    break;
            }

            foreach (string email in emailField.Trim().Split(EmailSeparater))
            {
                var trimmedEmail = email.Trim();
                if (trimmedEmail != string.Empty)
                {
                    int matchingUserCount = 0;
                    var matchingUsers = UserController.GetUsersByEmail(_portalId, trimmedEmail, 0, 999, ref matchingUserCount);
                    if (matchingUserCount > 0)
                    {
                        foreach (UserInfo user in matchingUsers)
                        {
                            AddRecipient(recipientList, user);
                        }
                    }
                    else
                    {
                        var user = new UserInfo() { UserID = Null.NullInteger, Email = trimmedEmail, DisplayName = trimmedEmail };
                        AddRecipient(recipientList, user);
                    }
                }
            }
        }
    
        private void AddRecipient(List<UserInfo> recipients, UserInfo user)
        {
            if (user.UserID == -1 || (user.Membership.Approved && !user.Membership.IsDeleted && !user.Membership.LockedOut))
            {
                var key = user.UserID == -1 ? user.Email : user.UserID.ToString();
                if (!_recipientKeys.Contains(key))
                {
                    recipients.Add(user);
                    _recipientKeys.Add(key);
                }
                else if (!RemoveDuplicates)
                {
                    recipients.Add(user);
                }
            }
        }

        private void AppendEmails(StringBuilder sb, List<UserInfo> recipients)
        {
            AppendEmails(sb, recipients, 0, true);
        }

        private void AppendEmails(StringBuilder sb, List<UserInfo> recipients, bool trimTrailingSeparator)
        {
            AppendEmails(sb, recipients, 0, trimTrailingSeparator);
        }

        private void AppendEmails(StringBuilder sb, List<UserInfo> recipients, int startIndex, bool trimTrailingSeparator)
        {
            for (int i = startIndex; i < recipients.Count; i++)
            {
                sb.Append(recipients[i].Email);
                sb.Append(EmailSeparater);
            }
            if (sb.Length > 0 && trimTrailingSeparator) sb.Length--;
        }

        private void EnsureNotDisposed()
        {
            if (_isDisposed)
            {
                throw new ObjectDisposedException("SharedDictionary");
            }
        }

        private string SendMail(string mailFrom, string mailTo, string mailCC, string mailBCC, string replyTo, DnnMail.MailPriority priority, string subject,
                                  List<System.Net.Mail.Attachment> attachments, MailFormat bodyFormat, System.Text.Encoding bodyEncoding, string body)
        {

            string result = string.Empty;

            try
            {
                result = Mail.SendMail(mailFrom, mailTo, mailCC, mailBCC, replyTo, priority, subject, bodyFormat, bodyEncoding, body, attachments, "", "", "", "", DotNetNuke.Entities.Host.Host.EnableSMTPSSL);
            }
            catch (Exception exc)
            {
                if (exc.InnerException != null)
                {
                    result = string.Concat(exc.Message, "\n\r", exc.InnerException.Message);
                    DotNetNuke.Services.Exceptions.Exceptions.LogException(exc.InnerException);
                }
                else
                {
                    result = exc.Message;
                    DotNetNuke.Services.Exceptions.Exceptions.LogException(exc);
                }
            }

            return result;
        }

        #endregion

        #region Public Methods

        public void AddAttachment(string localPath)
        {
            EnsureNotDisposed();

            _attachments.Add(new Attachment(localPath));
        }

        public void AddTokenizationDataSource(string dataSourceName, object dataSource)
        {
            EnsureNotDisposed();

            dataSourceName = dataSourceName.ToLowerInvariant();

            if (_tokenizationDataSources.ContainsKey(dataSourceName))
            {
                _tokenizationDataSources[dataSourceName] = dataSource;
            }
            else
            {
                _tokenizationDataSources.Add(dataSourceName, dataSource);
            }
        }

        public int BuildRecipientsList()
        {
            ClearRecipients();
            if (!string.IsNullOrEmpty(EmailTo)) AddRecipients(AddressFields.TO);
            if (!string.IsNullOrEmpty(EmailCC)) AddRecipients(AddressFields.CC);
            if (!string.IsNullOrEmpty(EmailBCC)) AddRecipients(AddressFields.BCC);

            if (EmailToRoles != null && EmailToRoles.Count > 0)
            {
                var rc = new RoleController();

                List<UserInfo> recipients;

                switch (EmailToRolesAddressField)
                {

                    case AddressFields.TO:
                        recipients = _recipientLists[(int)AddressFields.TO];
                        break;
                    case AddressFields.CC:
                        recipients = _recipientLists[(int)AddressFields.CC];
                        break;
                    case AddressFields.BCC:
                        recipients = _recipientLists[(int)AddressFields.BCC];
                        break;
                    default:
                        recipients = _recipientLists[(int)AddressFields.TO];
                        break;
                }

                foreach (string roleName in EmailToRoles)
                {
                    if (roleName != string.Empty)
                    {
                        var roleInfo = rc.GetRoleByName(_portalId, roleName);
                        if (roleInfo != null)
                        {
                            var users = RoleController.Instance.GetUsersByRole(_portalId, roleName);
                            foreach (UserInfo user in users)
                            {
                                var userInfo = user;
                                ProfileController.GetUserProfile(ref userInfo);
                                var userRole = rc.GetUserRole(_portalId, userInfo.UserID, roleInfo.RoleID);

                                if ((userRole.EffectiveDate <= DateTime.Now || Null.IsNull(userRole.EffectiveDate))
                                    && (userRole.ExpiryDate >= DateTime.Now || Null.IsNull(userRole.ExpiryDate)))
                                {
                                    AddRecipient(recipients, userInfo);
                                }
                            }
                        }
                    }
                }
            }

            var recipientCount = _recipientKeys.Count;
            _recipientListsBuilt = recipientCount > 0;
            return recipientCount;
        }

        public string Send()
        {
            string results = string.Empty;

            if (!_recipientListsBuilt) BuildRecipientsList();

            var to = new StringBuilder();
            var bcc = new StringBuilder();
            var cc = new StringBuilder();
            
            switch (SendMethod)
            {
                case SendMethods.Send_Default:
                    AppendEmails(to, _recipientLists[(int)AddressFields.TO], true);
                    AppendEmails(cc, _recipientLists[(int)AddressFields.CC], true);
                    AppendEmails(bcc, _recipientLists[(int)AddressFields.BCC], true);
                    break;
                case SendMethods.Send_TO:
                    _recipientLists[(int)AddressFields.TO].Concat(_recipientLists[(int)AddressFields.CC]).Concat(_recipientLists[(int)AddressFields.BCC]).ToList<UserInfo>();
                    break;
                case SendMethods.Send_CC:
                    AppendEmails(to, _recipientLists[(int)AddressFields.TO].Take(1).ToList<UserInfo>(), true);
                    AppendEmails(cc, _recipientLists[(int)AddressFields.TO].Skip(1).Concat(_recipientLists[(int)AddressFields.CC]).Concat(_recipientLists[(int)AddressFields.BCC]).ToList<UserInfo>(), true);
                    break;
                case SendMethods.Send_BCC:
                    AppendEmails(to, _recipientLists[(int)AddressFields.TO].Take(1).ToList<UserInfo>(), true);
                    AppendEmails(bcc, _recipientLists[(int)AddressFields.TO].Skip(1).Concat(_recipientLists[(int)AddressFields.CC]).Concat(_recipientLists[(int)AddressFields.BCC]).ToList<UserInfo>(), true);
                    break;
            }

            var emailFrom = string.IsNullOrEmpty(EmailFrom) ? SendingUser.Email : EmailFrom;
            var emailReplyTo = string.IsNullOrEmpty(EmailReplyTo) ? emailFrom : EmailReplyTo;

            var subject = Subject;
            var bodyFormat = (BodyFormat == MailFormat.Text ? MailFormat.Text : (HtmlUtils.IsHtml(Body) ? MailFormat.Html : MailFormat.Text));
            var body = bodyFormat == MailFormat.Html ? DotNetNuke.Common.Globals.ManageUploadDirectory(Body, _portalSettings.HomeDirectory) : Body;

            if (string.IsNullOrEmpty(subject) || string.IsNullOrEmpty(body) || _recipientKeys.Count == 0) return string.Empty;

            string tokenizedSubject = Subject;
            string tokenizedBody = Body;

            var tokenizer = new MultiObjectTokenReplace (DotNetNuke.Services.Tokens.Scope.DefaultSettings,  _sendingUser.Profile.PreferredLocale, _portalSettings, SendingUser, _moduleId, TokenizationDataSources);
            
            if (ReplaceTokens)
            {
                tokenizedSubject = tokenizer.ReplaceEnvironmentTokens(subject);
                tokenizedBody = tokenizer.ReplaceEnvironmentTokens(body);
            }

            if (debugWrite)
            {
                System.IO.StreamWriter fs = null;
                try
                {
                    fs = new System.IO.StreamWriter(_portalSettings.HomeDirectoryMapPath + "EmailLog.txt");
                    var sb = new StringBuilder();
                    sb.AppendLine("SendMethod: " + SendMethod.ToString());
                    sb.AppendLine("From: " + emailFrom);
                    sb.AppendLine("ReplyTo: " + EmailReplyTo);
                    sb.AppendLine("EmailTo: " + EmailTo);
                    sb.AppendLine("EmailRolesTo: " + EmailToRoles.Select(roleName => roleName + ';').ToString());
                    sb.AppendLine("EmailToRolesAddressField: " + EmailToRolesAddressField.ToString());
                    sb.AppendLine("To: " + to.ToString());
                    sb.AppendLine("CC: " + cc.ToString());
                    sb.AppendLine("BCC: " + bcc.ToString());
                    sb.AppendLine("-----------------------------------------------------------------------------");
                    sb.AppendLine("Attachments: " + Attachments.Count.ToString());
                    sb.AppendLine("BodyFormat: " + BodyFormat.ToString());
                    sb.AppendLine("Subject: " + tokenizedSubject);
                    sb.AppendLine();
                    sb.AppendLine("Body:");
                    sb.AppendLine(tokenizedBody);
                    fs.Write(sb.ToString());
                    fs.Flush();
                    results = "";
                }
                catch (Exception exc)
                {
                    results = exc.Message;
                }
                finally
                {
                    if (fs != null) fs.Close();
                }

            }
            else
            {
                if (SendMethod == SendMethods.Send_TO)
                {
                    foreach (UserInfo user in _recipientLists[(int)AddressFields.TO])
                    {
                        tokenizer.AccessingUser = user;
                        tokenizer.User = user;
                        tokenizer.Language = user.Profile.PreferredLocale;
                        tokenizedSubject = tokenizer.ReplaceEnvironmentalTokens(Subject);
                        tokenizedBody = tokenizer.ReplaceEnvironmentalTokens(Body);

                        results += SendMail(emailFrom, user.Email, "", "", emailReplyTo, MailPriority, tokenizedSubject,
                                  Attachments, BodyFormat, Encoding.UTF8, tokenizedBody);
                    }
                }
                else
                {
                    results = SendMail(emailFrom, to.ToString(), cc.ToString(), bcc.ToString(), emailReplyTo, MailPriority, tokenizedSubject,
                                  Attachments, BodyFormat, Encoding.UTF8, tokenizedBody);
                }
            }

            return results;
        }

        

        #endregion

        #region IDisposable Implementation

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!_isDisposed)
            {
                if (disposing)
                {
                    //get rid of managed resources
                    foreach (Attachment attachment in _attachments)
                    {
                        attachment.Dispose();
                        _isDisposed = true;
                    }
                }
                // get rid of unmanaged resources
            }
        }

        ~Mailer()
        {
            Dispose(false);
        }
        
        #endregion
    }
}